#include "seriesinstance.h"
#include "imageinstance.h"
#include "dcmimagecache.h"
#include "Global/globaldefs.h"

#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcdeftag.h"

SeriesInstance::SeriesInstance(const QString &seriesUID):
    seriesUid(seriesUID),
    curXYFrame(0),
    curXZFrame(0),
    curYZFrame(0),
    imgWidth(0),
    imgHeight(0),
    volPtr(0),
    volSlice(0),
    rawPtr(0),
    rawSlice(0),
    pola(EPP_Normal)
{
}

SeriesInstance::~SeriesInstance()
{
    emit aboutToDelete();
    delVolBuffer();
}

void SeriesInstance::delVolBuffer()
{
    delete[] volPtr;
    volPtr = 0;
    volSlice = 0;
    delete[] rawPtr;
    rawPtr = 0;
    rawSlice = 0;
}

bool SeriesInstance::insertImage(ImageInstance *image)
{
    if (image && image->isNormal() && image->getSeriesUid()==seriesUid) {
        if (imageMap.isEmpty()) {
            image->getWindow(winCenter, winWidth);
            image->getWindow(defCenter, defWidth);
            image->getImageSize(imgWidth, imgHeight);
            pola = image->getPolarity();
            curXYFrame = 0;
            curXZFrame = 0;
            curYZFrame = 0;
        }
        int imgNo = image->getTagKeyValue(DCM_InstanceNumber).toInt();
        if (imageMap.contains(imgNo))
            return false;
        imageMap.insert(imgNo, image->getImageFile());
        return true;
    }
    return false;
}

bool SeriesInstance::removeImage(const QString &imgFile)
{
    QMap<int, QString> map = imageMap;
    QMap<int, QString>::iterator i = map.begin();
    for (; i != map.end(); ++i) {
        if (i.value() == imgFile) {
            return imageMap.remove(i.key()) > 0;
        }
    }

    return false;
}

bool SeriesInstance::hasImage(const QString &file)
{
    foreach (const QString &path, imageMap.values()) {
        if (path == file) return true;
    }
    return false;
}

QString SeriesInstance::getTagKeyValue(const DcmTagKey &key) const
{
    QString value;

    if (key == DCM_NumberOfFrames) {
        int total = 0;
        foreach (const QString &f, imageMap.values()) {
            ImageInstance *inst = DcmImageCache::getCacher().getImage(f);
            int cnt = inst->getTagKeyValue(key).toInt();
            total += cnt?cnt:1;
        }
        value = QString::number(total);
    } else {
        ImageInstance *inst = getCurrImageInstance(DicomImageView::VT_XYPlane);
        if (inst) {
            value = inst->getTagKeyValue(key);
            if (key == DCM_PatientBirthDate || key == DCM_StudyDate ||
                    key == DCM_SeriesDate || key == DCM_InstanceCreationDate ||
                    key == DCM_AcquisitionDate || key == DCM_ContentDate) {
                QDate d = QDate::fromString(value, DICOM_DATE_FORMAT);
                value = d.isValid()?d.toString(NORMAL_DATE_FORMAT):value;
            } else if (key == DCM_StudyTime || key == DCM_SeriesTime ||
                       key == DCM_InstanceCreationTime || key == DCM_AcquisitionTime ||
                       key == DCM_ContentTime) {
                QTime t = QTime::fromString(value.left(6), DICOM_TIME_FORMAT);
                value = t.isValid()?t.toString(NORMAL_TIME_FORMAT):value;
            } else if (key == DCM_AcquisitionDateTime) {
                QDateTime t = QDateTime::fromString(value.left(14), DICOM_DATETIME_FORMAT);
                value = t.isValid()?t.toString(NORMAL_DATETIME_FORMAT):value;
            } else if (key == DCM_KVP) {
                value = QString::number(value.toDouble());
            }
        }
    }

    return value;
}

int SeriesInstance::getFrameCount(DicomImageView::ViewType type) const
{
    switch (type) {
    case DicomImageView::VT_XYPlane:
        return imageMap.values().size();
    case DicomImageView::VT_XZPlane:
        return imgHeight;
    case DicomImageView::VT_YZPlane:
        return imgWidth;
    }
    return 0;
}

const short **SeriesInstance::getSeriesVolume(const short **&volume, ulong &width, ulong &height, ulong &slice)
{
    volume = 0;
    if (imageMap.isEmpty()) return volume;

    if ((!volPtr) || imageMap.values().size() != volSlice) {
        delete[] volPtr;
        volSlice = imageMap.values().size();
        volPtr = new const short*[volSlice];
        ImageInstance *image;
        for (ulong i = 0; i < volSlice; ++i) {
            image = DcmImageCache::getCacher().getImage(imageMap.values().at(i));
            volPtr[i] = image->getInternalPtr();
        }
    }
    volume = volPtr;
    width = imgWidth;
    height = imgHeight;
    slice = volSlice;
    return volume;
}

const ushort **SeriesInstance::getRawVolume(const ushort **&volume, ulong &width, ulong &height, ulong &slice)
{
    volume = 0;
    if (imageMap.isEmpty()) return volume;

    if ((!rawPtr) || imageMap.values().size() != rawSlice) {
        delete[] rawPtr;
        rawSlice = imageMap.values().size();
        rawPtr = new const ushort*[rawSlice];
        ImageInstance *image;
        for (ulong i = 0; i < rawSlice; ++i) {
            image = DcmImageCache::getCacher().getImage(imageMap.values().at(i));
            rawPtr[i] = image->getRawData();
        }
    }
    volume = rawPtr;
    width = imgWidth;
    height = imgHeight;
    slice = rawSlice;
    return volume;
}

ImageInstance *SeriesInstance::getCurrImageInstance(DicomImageView::ViewType type) const
{
    if ((!imageMap.isEmpty()) && DicomImageView::VT_XYPlane == type)
        return DcmImageCache::getCacher().getImage(imageMap.values().at(curXYFrame));
    else return 0;
}

bool SeriesInstance::getPixmap(QPixmap &pixmap, DicomImageView::ViewType type)
{
    if (imageMap.isEmpty()) return false;

    ImageInstance *image;
    const short **volume;
    ulong w, h, s, rh;
    switch (type) {
    case DicomImageView::VT_XYPlane:
        image = getCurrImageInstance(DicomImageView::VT_XYPlane);
        if (winWidth < 1) winWidth = 1;
        image->setWindow(winCenter, winWidth);
        image->setPolarity(pola);
        return image->getPixmap(pixmap);
        break;
    case DicomImageView::VT_XZPlane:
        if (getSeriesVolume(volume, w, h, s)) {
            rh = s;//((s+3)/4)*4;
            BITMAPFILEHEADER lpfh;
            BITMAPINFOHEADER lpih;
            RGBQUAD palette[256];

            memset(&lpfh, 0, sizeof(BITMAPFILEHEADER));
            lpfh.bfType = 0x4d42;  //'B''M'
            lpfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(palette);

            memset(&lpih, 0, sizeof(BITMAPINFOHEADER));
            lpih.biSize = sizeof(BITMAPINFOHEADER);
            lpih.biWidth = w;
            lpih.biHeight = rh;
            lpih.biCompression = BI_RGB;
            lpih.biBitCount = 8;
            lpih.biPlanes = 1;

            memset(palette, 0, sizeof(RGBQUAD));
            for (int i = 0; i < 256; ++i) {
                palette[i].rgbBlue = i;
                palette[i].rgbGreen = i;
                palette[i].rgbRed = i;
            }

            ulong size = w * rh;
            uchar *pDIB = new uchar[size];
            memset(pDIB, 0, sizeof(uchar)*size);
            double center = winCenter;
            double width = winWidth;
            double factor = 255 / width;
            double lower = center-width/2;
            for (uint y = 0; y < s; ++y) {
                const short *ptr = volume[y];
                int idx = curXZFrame * w;
                int bmpIdx = (s-1-y) * w;
                if (ptr) {
                    for (uint x = 0; x < w; ++x) {
                        short val = ptr[idx+x];
                        if (val > lower+width)
                            pDIB[bmpIdx+x] = 255;
                        else if (val > lower)
                            pDIB[bmpIdx+x] = (val-lower)*factor;
                    }
                }
            }

            //lpih.biSizeImage = size;
            lpfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(palette) + size;

            QByteArray bmp;
            bmp.append((char*)&lpfh, sizeof(BITMAPFILEHEADER));
            bmp.append((char*)&lpih, sizeof(BITMAPINFOHEADER));
            bmp.append((char*)palette, sizeof(palette));
            bmp.append((char*)pDIB, size);

            delete[] pDIB;
            return pixmap.loadFromData(bmp);
        }
        break;
    case DicomImageView::VT_YZPlane:
        if (getSeriesVolume(volume, w, h, s)) {
            rh = s;//((s+3)/4)*4;
            BITMAPFILEHEADER lpfh;
            BITMAPINFOHEADER lpih;
            RGBQUAD palette[256];

            memset(&lpfh, 0, sizeof(BITMAPFILEHEADER));
            lpfh.bfType = 0x4d42;  //'B''M'
            lpfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(palette);

            memset(&lpih, 0, sizeof(BITMAPINFOHEADER));
            lpih.biSize = sizeof(BITMAPINFOHEADER);
            lpih.biWidth = h;
            lpih.biHeight = rh;
            lpih.biCompression = BI_RGB;
            lpih.biBitCount = 8;
            lpih.biPlanes = 1;

            memset(palette, 0, sizeof(palette));
            for (int i = 0; i < 256; ++i) {
                palette[i].rgbBlue = i;
                palette[i].rgbGreen = i;
                palette[i].rgbRed = i;
            }

            ulong size = h * rh;
            uchar *pDIB = new uchar[size];
            memset(pDIB, 0, sizeof(uchar)*size);
            double center = winCenter;
            double width = winWidth;
            double factor = 255 / width;
            double lower = center-width/2;
            for (uint y = 0; y < s; ++y) {
                const short *ptr = volume[y];
                int bmpIdx = (s-1-y) * h;
                if (ptr) {
                    for (uint x = 0; x < h; ++x) {
                        short val = ptr[x*w+curYZFrame];
                        if (val > lower+width) pDIB[bmpIdx+x] = 255;
                        else if (val > lower) pDIB[bmpIdx+x] = (val-lower)*factor;
                    }
                }
            }

            //lpih.biSizeImage = size;
            lpfh.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(palette) + size;

            QByteArray bmp;
            bmp.append((char*)&lpfh, sizeof(BITMAPFILEHEADER));
            bmp.append((char*)&lpih, sizeof(BITMAPINFOHEADER));
            bmp.append((char*)palette, sizeof(palette));
            bmp.append((char*)pDIB, size);

            delete[] pDIB;
            return pixmap.loadFromData(bmp);
        }
        break;
    }

    return false;
}

void SeriesInstance::nextFrame(DicomImageView::ViewType type)
{
    switch (type) {
    case DicomImageView::VT_XYPlane:
        curXYFrame++;
        if (curXYFrame >= imageMap.values().size()) curXYFrame = 0;
        break;
    case DicomImageView::VT_XZPlane:
        curXZFrame++;
        if (curXZFrame >= imgHeight) curXZFrame = 0;
        break;
    case DicomImageView::VT_YZPlane:
        curYZFrame++;
        if (curYZFrame >= imgWidth) curYZFrame = 0;
        break;
    }
}

void SeriesInstance::prevFrame(DicomImageView::ViewType type)
{
    if (imageMap.isEmpty()) return;
    switch (type) {
    case DicomImageView::VT_XYPlane:
        curXYFrame--;
        if (curXYFrame < 0) curXYFrame = imageMap.values().size() - 1;
        break;
    case DicomImageView::VT_XZPlane:
        curXZFrame--;
        if (curXZFrame < 0) curXZFrame = imgHeight - 1;
        break;
    case DicomImageView::VT_YZPlane:
        curYZFrame--;
        if (curYZFrame < 0) curYZFrame = imgWidth - 1;
        break;
    }
}

void SeriesInstance::gotoFrame(int index, DicomImageView::ViewType type)
{
    switch (type) {
    case DicomImageView::VT_XYPlane:
        if (index < 0) curXYFrame = 0;
        else if (index >= imageMap.values().size()) curXYFrame = imageMap.values().size() - 1;
        else curXYFrame = index;
        break;
    case DicomImageView::VT_XZPlane:
        if (index < 0) curXZFrame = 0;
        else if (index >= imgHeight) curXZFrame = imgHeight - 1;
        else curXZFrame = index;
        break;
    case DicomImageView::VT_YZPlane:
        if (index < 0) curYZFrame = 0;
        else if (index >= imgWidth) curYZFrame = imgWidth - 1;
        else curYZFrame = index;
        break;
    }
}

int SeriesInstance::getCurIndex(DicomImageView::ViewType type)
{
    switch (type) {
    case DicomImageView::VT_XYPlane:
        return curXYFrame;
    case DicomImageView::VT_XZPlane:
        return curXZFrame;
    case DicomImageView::VT_YZPlane:
        return curYZFrame;
    }
    return 0;
}

void SeriesInstance::setRoiWindow(const QRectF &rect)
{
    if (imageMap.isEmpty()) return;
    ImageInstance *image = DcmImageCache::getCacher().getImage(imageMap.values().at(curXYFrame));
    image->setRoiWindow(rect);
    image->getWindow(winCenter, winWidth);
}

void SeriesInstance::setFullDynamic()
{
    if (imageMap.isEmpty()) return;
    ImageInstance *image = DcmImageCache::getCacher().getImage(imageMap.values().at(curXYFrame));
    image->setFullDynamic();
    image->getWindow(winCenter, winWidth);
}

void SeriesInstance::setPolarity(EP_Polarity polarity)
{
    if (imageMap.isEmpty()) return;
    ImageInstance *image = DcmImageCache::getCacher().getImage(imageMap.values().at(curXYFrame));
    image->setPolarity(polarity);
    pola = image->getPolarity();
}

bool SeriesInstance::getPixSpacing(double &spacingX, double &spacingY, DicomImageView::ViewType type) const
{
    double sx, sy, sz;
    if (imageMap.isEmpty()) return false;
    ImageInstance *image = DcmImageCache::getCacher().getImage(imageMap.values().first());
    if (!image->getPixSpacing(sx, sy)) return false;
    sz = image->getTagKeyValue(DCM_SliceThickness).toDouble();

    switch (type) {
    case DicomImageView::VT_XYPlane:
        spacingX = sx;
        spacingY = sy;
        break;
    case DicomImageView::VT_XZPlane:
        if (sz <= 0) return false;
        spacingX = sx;
        spacingY = sz;
        break;
    case DicomImageView::VT_YZPlane:
        if (sz <= 0) return false;
        spacingX = sy;
        spacingY = sz;
        break;
    default:
        return false;
    }
    return true;
}

double SeriesInstance::getPixelValue(long x, long y, DicomImageView::ViewType type) const
{
    if (!imageMap.isEmpty()) {
        switch (type) {
        case DicomImageView::VT_XYPlane:
            return DcmImageCache::getCacher().getImage(imageMap.values().at(curXYFrame))->getPixelValue(x, y);
            break;
        case DicomImageView::VT_XZPlane:
            if (y >= 0 && y < imageMap.values().size())
                return DcmImageCache::getCacher().getImage(imageMap.values().at(y))->getPixelValue(x, curXZFrame);
            break;
        case DicomImageView::VT_YZPlane:
            if (y >= 0 && y < imageMap.values().size())
                return DcmImageCache::getCacher().getImage(imageMap.values().at(y))->getPixelValue(curYZFrame, x);
            break;
        }
    }

    return 0;
}
